// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#pragma once

#include <ddk/mmio-buffer.h>
#include <ddktl/protocol/pciroot.h>
#include <endian.h>
#include <hwreg/bitfields.h>
#include <lib/mmio/mmio.h>
#include <stdio.h>
#include <zircon/hw/pci.h>
#include <zircon/types.h>

namespace pci {
namespace config {

// Fields correspond to the name in the PCI Local Bus Spec section 6.2.
struct Command {
  uint16_t value;
  // 15-11 are reserved preserve.
  DEF_SUBBIT(value, 10, interrupt_disable);
  DEF_SUBBIT(value, 9, fast_back_to_back_enable);
  DEF_SUBBIT(value, 8, serr_enable);
  // 7 is reserved preserve.
  DEF_SUBBIT(value, 6, parity_error_response);
  DEF_SUBBIT(value, 5, vga_palette_snoop);
  DEF_SUBBIT(value, 4, memory_write_and_invalidate_enable);
  DEF_SUBBIT(value, 3, special_cycles);
  DEF_SUBBIT(value, 2, bus_master);
  DEF_SUBBIT(value, 1, memory_space);
  DEF_SUBBIT(value, 0, io_space);
};

// The layout of a Base Address Register changes based on its type.
// PCI Local Bus Spec section 6.2.5.1.
struct IoBaseAddress;
struct MmioBaseAddress;
struct BaseAddress : public hwreg::RegisterBase<BaseAddress, uint32_t> {
  DEF_RSVDZ_BIT(1);
  DEF_BIT(0, is_io_space);

  IoBaseAddress* io() { return reinterpret_cast<IoBaseAddress*>(this); }
  MmioBaseAddress* mmio() { return reinterpret_cast<MmioBaseAddress*>(this); }
  static auto Get() { return hwreg::RegisterAddr<BaseAddress>(0); }
};

struct IoBaseAddress : public BaseAddress {
  DEF_UNSHIFTED_FIELD(31, 2, base_address);
};

struct MmioBaseAddress : public BaseAddress {
  DEF_UNSHIFTED_FIELD(31, 4, base_address);
  DEF_BIT(3, is_prefetchable);
  DEF_BIT(2, is_64_bit);
};

}  // namespace config

constexpr zx_paddr_t bdf_to_ecam_offset(pci_bdf_t bdf, uint8_t start_bus) {
  // Find the offset into the ecam region for the given bdf address. Every bus
  // has 32 devices, every device has 8 functions, and each function has an
  // extended config space of 4096 bytes. The base address of the vmo provided
  // to the bus driver corresponds to the start_bus_num, so offset the bdf address
  // based on the bottom of our ecam.
  zx_vaddr_t bdf_start = (bdf.bus_id - start_bus) * PCIE_ECAM_BYTES_PER_BUS;
  bdf_start += bdf.device_id * PCI_MAX_FUNCTIONS_PER_DEVICE * PCIE_EXTENDED_CONFIG_SIZE;
  bdf_start += bdf.function_id * PCIE_EXTENDED_CONFIG_SIZE;
  return bdf_start;
}

class PciReg8 {
 public:
  constexpr explicit PciReg8(uint16_t offset) : offset_(offset) {}
  constexpr PciReg8() : offset_(0u) {}
  constexpr uint16_t offset() const { return offset_; }
  inline bool operator==(const PciReg8& other) { return (offset() == other.offset()); }

 private:
  uint16_t offset_;
};

class PciReg16 {
 public:
  constexpr explicit PciReg16(uint16_t offset) : offset_(offset) {}
  constexpr PciReg16() : offset_(0u) {}
  constexpr uint16_t offset() const { return offset_; }
  inline bool operator==(const PciReg8& other) { return (offset() == other.offset()); }

 private:
  uint16_t offset_;
};

class PciReg32 {
 public:
  constexpr explicit PciReg32(uint16_t offset) : offset_(offset) {}
  constexpr PciReg32() : offset_(0u) {}
  constexpr uint16_t offset() const { return offset_; }
  inline bool operator==(const PciReg8& other) { return (offset() == other.offset()); }

 private:
  uint16_t offset_;
};

// Config supplies the factory for creating the appropriate pci config
// object based on the address space of the pci device.
class Config {
 public:
  // Standard PCI configuration space values. Offsets from PCI Firmware Spec ch 6.
  static constexpr PciReg16 kVendorId = PciReg16(0x0);
  static constexpr PciReg16 kDeviceId = PciReg16(0x2);
  static constexpr PciReg16 kCommand = PciReg16(0x4);
  static constexpr PciReg16 kStatus = PciReg16(0x6);
  static constexpr PciReg8 kRevisionId = PciReg8(0x8);
  static constexpr PciReg8 kProgramInterface = PciReg8(0x9);
  static constexpr PciReg8 kSubClass = PciReg8(0xA);
  static constexpr PciReg8 kBaseClass = PciReg8(0xB);
  static constexpr PciReg8 kCacheLineSize = PciReg8(0xC);
  static constexpr PciReg8 kLatencyTimer = PciReg8(0xD);
  static constexpr PciReg8 kHeaderType = PciReg8(0xE);
  static constexpr PciReg8 kBist = PciReg8(0xF);
  // 0x10 is the address of the first BAR in config space
  // BAR rather than BaseAddress for space / sanity considerations
  static constexpr PciReg32 kBar(uint32_t bar) {
    ZX_ASSERT(bar < PCI_MAX_BAR_REGS);
    return PciReg32(static_cast<uint16_t>(0x10 + (bar * sizeof(uint32_t))));
  }
  static constexpr PciReg32 kCardbusCisPtr = PciReg32(0x28);
  static constexpr PciReg16 kSubsystemVendorId = PciReg16(0x2C);
  static constexpr PciReg16 kSubsystemId = PciReg16(0x2E);
  static constexpr PciReg32 kExpansionRomAddress = PciReg32(0x30);
  static constexpr PciReg8 kCapabilitiesPtr = PciReg8(0x34);
  // 0x35 through 0x3B is reserved
  static constexpr PciReg8 kInterruptLine = PciReg8(0x3C);
  static constexpr PciReg8 kInterruptPin = PciReg8(0x3D);
  static constexpr PciReg8 kMinGrant = PciReg8(0x3E);
  static constexpr PciReg8 kMaxLatency = PciReg8(0x3F);
  static constexpr uint8_t kStdCfgEnd =
      static_cast<uint8_t>(kMaxLatency.offset() + sizeof(uint8_t));

  // pci to pci bridge config
  // Unlike a normal PCI header, a bridge only has two BARs, but the BAR offset in config space
  // is the same.
  static constexpr PciReg8 kPrimaryBusId = PciReg8(0x18);
  static constexpr PciReg8 kSecondaryBusId = PciReg8(0x19);
  static constexpr PciReg8 kSubordinateBusId = PciReg8(0x1A);
  static constexpr PciReg8 kSecondaryLatencyTimer = PciReg8(0x1B);
  static constexpr PciReg8 kIoBase = PciReg8(0x1C);
  static constexpr PciReg8 kIoLimit = PciReg8(0x1D);
  static constexpr PciReg16 kSecondaryStatus = PciReg16(0x1E);
  static constexpr PciReg16 kMemoryBase = PciReg16(0x20);
  static constexpr PciReg16 kMemoryLimit = PciReg16(0x22);
  static constexpr PciReg16 kPrefetchableMemoryBase = PciReg16(0x24);
  static constexpr PciReg16 kPrefetchableMemoryLimit = PciReg16(0x26);
  static constexpr PciReg32 kPrefetchableMemoryBaseUpper = PciReg32(0x28);
  static constexpr PciReg32 kPrefetchableMemoryLimitUpper = PciReg32(0x2C);
  static constexpr PciReg16 kIoBaseUpper = PciReg16(0x30);
  static constexpr PciReg16 kIoLimitUpper = PciReg16(0x32);
  // Capabilities Pointer for a bridge matches the standard 0x34 offset
  // 0x35 through 0x38 is reserved
  static constexpr PciReg32 kBridgeExpansionRomAddress = PciReg32(0x38);
  // interrupt line for a bridge matches the standard 0x3C offset
  // interrupt pin for a bridge matches the standard 0x3D offset
  static constexpr PciReg16 kBridgeControl = PciReg16(0x3E);

  // Create a Pci Configuration object of the appropriate type.
  //
  // @param base The base address for the PCI configuration space.  @param
  // @param addr_type An enum value of PciAddrSpace to identify the time of address
  // space the configuration object will use.
  //
  // @return a pointer to a new Config instance on success, nullptr on failure.
  //
  inline const pci_bdf_t& bdf() const { return bdf_; }
  inline const char* addr(void) const { return addr_; }
  virtual const char* type(void) const = 0;

  // Virtuals
  void DumpConfig(uint16_t len) const;
  virtual uint8_t Read(const PciReg8 addr) const = 0;
  virtual uint16_t Read(const PciReg16 addr) const = 0;
  virtual uint32_t Read(const PciReg32 addr) const = 0;
  virtual void Write(const PciReg8 addr, uint8_t val) const = 0;
  virtual void Write(const PciReg16 addr, uint16_t val) const = 0;
  virtual void Write(const PciReg32 addr, uint32_t val) const = 0;
  virtual ~Config() {}

 protected:
  Config(pci_bdf_t bdf) : bdf_(bdf) {
    snprintf(addr_, sizeof(addr_), "%02x:%02x.%01x", bdf_.bus_id, bdf_.device_id, bdf_.function_id);
  }
  const pci_bdf_t bdf_;
  char addr_[8];
};

// MMIO config is the stardard method for accessing modern pci configuration space.
// A device's configuration space is mapped to a specific place in a given pci root's
// ecam and can be directly accessed with standard IO operations.t
class MmioConfig : public Config {
 public:
  static zx_status_t Create(pci_bdf_t bdf, ddk::MmioBuffer* ecam_, uint8_t start_bus,
                            uint8_t end_bus, std::unique_ptr<Config>* config);
  uint8_t Read(const PciReg8 addr) const final;
  uint16_t Read(const PciReg16 addr) const final;
  uint32_t Read(const PciReg32 addr) const final;
  void Write(const PciReg8 addr, uint8_t val) const final;
  void Write(const PciReg16 addr, uint16_t val) const final;
  void Write(const PciReg32 addr, uint32_t val) const override;
  const char* type(void) const override;

 private:
  friend class FakeMmioConfig;
  MmioConfig(pci_bdf_t bdf, ddk::MmioView&& view) : Config(bdf), view_(std::move(view)) {}
  const ddk::MmioView view_;
};

// ProxyConfig is used with PCI buses that do not support MMIO config space,
// or require special controller configuration before config access. Examples
// of this are IO config on x64 due to needing to synchronize CF8/CFC with
// ACPI, and Designware on ARM where the controller needs to be configured to
// map a given device's configuration space in before access.
//
// For proxy configuration access all operations are passed to the pciroot
// protocol implementation hosted in the same devhost as the pci bus driver.
class ProxyConfig final : public Config {
 public:
  static zx_status_t Create(pci_bdf_t bdf, ddk::PcirootProtocolClient* proto,
                            std::unique_ptr<Config>* config);
  uint8_t Read(const PciReg8 addr) const final;
  uint16_t Read(const PciReg16 addr) const final;
  uint32_t Read(const PciReg32 addr) const final;
  void Write(const PciReg8 addr, uint8_t val) const final;
  void Write(const PciReg16 addr, uint16_t val) const final;
  void Write(const PciReg32 addr, uint32_t val) const final;
  const char* type(void) const final;

 private:
  ProxyConfig(pci_bdf_t bdf, ddk::PcirootProtocolClient* proto) : Config(bdf), pciroot_(proto) {}
  // The bus driver outlives config objects.
  ddk::PcirootProtocolClient* const pciroot_;
};

}  // namespace pci
